#!/usr/bin/env python
# coding: utf-8
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from google.appengine.api import memcache

from google.appengine.ext.search import SearchableModel

# データストアの使用
# https://developers.google.com/appengine/docs/python/gettingstarted/usingdatastore?hl=ja
from google.appengine.ext import db

from google.appengine.ext.webapp.util import run_wsgi_app
import webapp2
import cgi
#import os

# ユーザ認証関係
# https://developers.google.com/appengine/docs/python/users/overview?hl=ja
from google.appengine.ext.webapp.util import login_required
from google.appengine.api import users

import re
import time
import logging

from google.appengine.ext import search
from google.appengine.ext.db import *

# 『時間がかかる処理はバックグラウンドに回して、リクエストハンドラーはさっさとブラウザーに応答する』というのは、Webアプリケーションの作成では基本的なテクニックみたいです。
# http://www.syousei.jp/2012/11/26/27
from google.appengine.api import taskqueue

# https://developers.google.com/appengine/docs/python/images/usingimages?hl=ja
from google.appengine.api import images

def str2int(str_in):
    if str_in is None:
        return 0
    if len(str_in) == 0:
        return 0
    return int(str_in)

def str2fl(str_in):
    if str_in is None:
        return 0.0
    if len(str_in) == 0:
        return 0.0
    return float(str_in)

# DB間のリンクについて:
# https://groups.google.com/forum/?fromgroups=#!topic/google-appengine/dCZ3HBhG4RM
class mvc_itemtbl(search.SearchableModel):
    date        = db.DateTimeProperty(auto_now_add=True)
    name        = db.StringProperty(u"name")
    name_old    = db.StringProperty(indexed=False)
    author      = db.UserProperty()
    review      = db.IntegerProperty(default=0)

class mvc_itembak(db.Model):
    date        = db.DateTimeProperty(auto_now_add=True)
    name        = db.StringProperty(u"name")
    name_old    = db.StringProperty(indexed=False)
    author      = db.UserProperty()
    ip          = db.StringProperty()

class mvc_itemtrash(db.Model):
    date        = db.DateTimeProperty(auto_now_add=True)
    name        = db.StringProperty(u"name")
    name_old    = db.StringProperty(indexed=False)
    author      = db.UserProperty()
    ip          = db.StringProperty()

class mvc_addrtbl(db.Model):
    server  = db.IntegerProperty()
    area    = db.IntegerProperty()
    alter   = db.IntegerProperty()
    address = db.StringProperty()
    xc      = db.FloatProperty(indexed=False)
    zc      = db.FloatProperty(indexed=False)

class mvc_headtbl(db.Model):
    date    = db.DateTimeProperty(auto_now_add=True)
    price   = db.IntegerProperty(default=0)
    item_ref = db.ReferenceProperty(mvc_itemtbl)
    addr_ref = db.ReferenceProperty(mvc_addrtbl)

class moe_maptbl(db.Model):
    author = db.UserProperty()
    content = db.StringProperty(multiline=True)
    avatar = db.BlobProperty()
    date = db.DateTimeProperty(auto_now_add=True)

def name_cache_header_count():
    itemlist = memcache.get('itemlist')
    if itemlist is None:
        return 0
    return len(itemlist)

def name_cache_header_dump(out_list):
    # memcacheの名前一覧を返す
    itemlist = memcache.get('itemlist')
    if itemlist is None:
        return 0
    for item in itemlist:
        # 中身はなかったが、名前リストにはあった: 名前リストから削除
        entity = memcache.get(item)
        if entity is None:
            itemlist.remove(item)
            memcache.set('itemlist', itemlist)
            continue
        # 戻り値へ追加する
        out_list.append(item)
    return len(itemlist)

def name_cache_list_append(item_name):
    itemlist = memcache.get('itemlist')
    if itemlist is None:
        itemlist = []   # 最初の一回だけ
    if item_name in itemlist:
        return  # 既に追加されていた
    #logging.info(type(item_name))
    itemlist.append(item_name)
    # 半日
    exptime = 3600*12
    memcache.set('itemlist', itemlist, time=exptime)

def name_cache_append(item_name, entity):
    ret = memcache.get(item_name)
    if ret is None:
        name_cache_list_append(item_name)
    # 反日
    exptime = 3600*12
    return memcache.set(item_name, entity, time=exptime)

def name_cache_get(item_name):
    return memcache.get(item_name)

def addr_cache_get(server, area, alter):
    # 番地情報キャッシュ読み出し
    addr_cache_list = memcache.get('addr_list')
    if addr_cache_list is None:
        return 0
    cache_addr = 'server%d_area%d_alter%d' % (server, area, alter)
    for ac in addr_cache_list:
        if ac == cache_addr:
            return 1
    return 0

def addr_cache_append(server, area, alter):
    # 番地情報キャッシュに登録
    addr_cache_list = memcache.get('addr_list')
    if addr_cache_list is None:
        addr_cache_list = []
    cache_addr = 'server%d_area%d_alter%d' % (server, area, alter)
    if cache_addr not in addr_cache_list:
        addr_cache_list.append(cache_addr)
        memcache.set('addr_list', addr_cache_list, time=3600)

class MainPage(webapp2.RequestHandler):

    def display_cache(self, qitem_key):
        # キャッシュを表示
        self.response.write('<table class="tblbd">\n')
        self.response.write('<tr><td align=center>品名</td><td align=right>&nbsp;</td>\n')
        self.response.write('</tr>\n')
        name_list = []
        cache_count = name_cache_header_dump(name_list)
        self.response.write('%d件のキャッシュを使用しました。<BR>&nbsp;&nbsp;(※ 最近追加または検索されたアイテム)<BR>\n' %(cache_count) )
        for cached in name_list:
            #logging.info(type(cached))
            cached = re.sub('<', '&lt', cached)
            cached = re.sub('>', '&gt', cached)
            try:
                cached = cached.encode('utf_8')
            except UnicodeDecodeError:
                pass
            elm = name_cache_get(cached)
            if elm is None:
                continue

            self.response.write('<tr>\n')
            self.response.write('<td align=right>%s</td>\n' % (cached) )
            self.response.write('<td><a href="/?item=&quot%s&quot">検索</a><!-- %s --></td>\n' % (cached, type(cached) ) )
            self.response.write('</tr>\n')
        self.response.write('</table>\n')
        self.response.write('%s\n' % '</BR>' * 10)

    def get(self):
        per_page = 20
        max_page = 5
        max_read = per_page * max_page

        prev_cursor = self.request.get('prev_cursor')
        next_cursor = self.request.get('next_cursor')

        qitem = self.request.get('item')
        qpage = self.request.get('page')
        ipage = str2int(qpage)
        mdq = re.search('"', qitem)
        if mdq is not None:
            qitem = re.sub('"', '&quot', qitem)
        page_title = u'MoE Vendor Search 結果'
        if qitem is not '':
            page_title += u'(' + qitem + ')'

        qitem_key = re.sub('&quot', '', qitem)

        self.response.write("""
        <html><HEAD><TITLE>%s</TITLE></HEAD><body>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<link media="only screen and (max-device-width:480px)" href="/stylesheets/sm.css" type="text/css" rel="stylesheet" />
<link media="screen and (min-device-width:481px)" href="/stylesheets/main.css" type="text/css" rel="stylesheet" />

<!--[if IE]>
<link href="/stylesheets/main.css" type="text/css" rel="stylesheet" />
<![endif]-->

    """ % page_title)

        dispcache = str2int(self.request.get('dispcache'))
        if dispcache == 1:
            self.display_cache(qitem_key)
            return

        ua = self.request.headers['User-Agent']
        if re.match('Googlebot', ua) or re.match('wget', ua): # or Googlebot-Mobile
            self.response.write("To see details for item:%s, use proper browser</BR></body></html>\n" % qitem)
            logging.info(ua)
            return

        self.response.write("<!-- item:%s-->\n" % qitem)
        qf3=''
        if len(qitem) > 1:
            qf3 = qitem.encode('utf_8')

        # Write the submission form and the footer of the page
        self.response.write("""
    <form action="/" method="get">
      <table class="tblbd">
        <tr>
          <TD> アイテム名 :<BR> 
            <input type="text" name="item" value="%s" size="30" />
    """  % (qf3) )

        self.response.write("""
            <TD><input type="submit" value="検索"></TD>
        </tr>
      </table>
    </form>
    """ )
        if len(qitem) == 0:
            self.response.write("検索条件(アイテム名)を入れてください。<BR>")
            return
        if len(qitem) < 3:
            self.response.write("検索条件が曖昧です。3文字以上入れてください。<BR>")
            return

        records = None
        next_page = False
        # ダブルクオートで囲ってある場合は、一致チェックする。
        rec_cnt = 0
        data_source = 0
        if mdq is not None:
            # ここは完全一致を探すから、Limitチェックしなくていいだろ・・・
            self.response.write("<!-- item_key:%s -->\n" % qitem_key)
            sql1 = u"SELECT * FROM mvc_itemtbl WHERE name = '%s'" % qitem_key
            records = db.GqlQuery(sql1)
            rec_cnt = records.count()
            self.response.write("<!-- item_key_found:%d -->\n" % rec_cnt)
            if rec_cnt:
                data_source = 1
        if rec_cnt == 0:
            # アイテムテーブルから指定アイテム名を検索する
            q = mvc_itemtbl.all().search(qitem_key)

            # 前回のカーソルが登録されていれば次位置から取得する
            if next_cursor:
                records = q.with_cursor(start_cursor=next_cursor).search(qitem_key).fetch(limit=per_page)
            else:
                records = q.search(qitem_key).fetch(limit=per_page)

            next_cursor = q.cursor()             # 今の終了位置 → 次の開始位置

            rec_cnt = len(records)
            if rec_cnt > per_page:
                next_page = True
                rec_cnt = per_page
            self.response.write('<!-- all().search().fetch returns: %d -->\n' % (rec_cnt) )
            if rec_cnt == 0:
                self.response.write('一致なし\n')
                self.response.write("</body></html>\n")
                return

        # ページリンクを表示する
        if rec_cnt >= per_page:
            self.response.write(u'<a HREF="/?page=%d&item=%s&next_cursor=%s&">次%d件(Page%d)</A> &nbsp;\n' % (ipage+1, qitem, next_cursor, per_page, ipage+2 ) )
        else:
            self.response.write(u'<a HREF="/?page=%d&item=%s&">最初へ戻る </A> &nbsp;\n' % (0, qitem) )
        self.response.write('</BR>\n')
        self.response.write('</BR>\n')

        # 表の表示開始
        self.response.write('<table class="tblbd">\n')
        self.response.write('<tr><td align=center>品名</td><td align=center>詳細</td><td align=center>&nbsp;</td><td align=center>削除</td>\n')
        self.response.write('</tr>\n')
        read_start = ipage * per_page
        if rec_cnt - ipage * per_page < per_page:
            read_end = rec_cnt % per_page
        else:
            read_end = per_page
        #logging.info('read_start:%d %d read_end:%d' % ( ipage, read_start, read_end) )

        cnt = 0
        for elm in records:
            name1 = elm.name
            name1 = re.sub('<', '&lt', name1)
            name1 = re.sub('>', '&gt', name1)
            name1 = name1.encode('utf_8')
            item_key = elm.key().name()
            self.response.write('<tr>\n')
            self.response.write('<td>%s</td>\n' % (name1) )
            self.response.write('<td><a href="/Detail?' )
            self.response.write('item_key=%s&item=%s">\n' % (item_key, qitem_key) )
            self.response.write('表&nbsp;示</a></td>\n')
            self.response.write('<TD><a href="/Modify?item_key=%s&item=%s">' % (item_key, qitem_key) )
            self.response.write('修&nbsp;正</a></td>\n')
            self.response.write(u"""
<TD>
    <form action="/DeleteItemConfirm" method="post">
        <input type="hidden" name="item_key" value="%s" />
        <input type="submit" value="削除" />
    </form>
</TD>
""" % item_key)
            self.response.write('</tr>\n')
            cnt += 1
            name_cache_append(name1, elm)
        self.response.write('</table>\n')
        if data_source:
            self.response.write('完全一致検索で%d件のアイテムがヒットしました。<BR>\n' %(cnt) )
        else:
            self.response.write('インデックス検索で%d件のアイテムがヒットしました。<BR>\n' %(cnt) )

        self.response.write('%s\n' % '</BR>' * 1)

        serial = name_cache_header_count()
        self.response.write('<a href=/?dispcache=1&>%d件</a>のキャッシュを使用しました。<BR>\n' %(serial) )

        self.response.write('%s\n' % '</BR>' * 10)

        self.response.write('番地情報キャッシュ: </BR>\n')
        addr_cache_list = memcache.get('addr_list')
        if addr_cache_list is not None:
            for ac in addr_cache_list:
                self.response.write('%s</BR>\n' % ac)

        self.response.write("</body></html>\n")

class ItemDetail(webapp2.RequestHandler):
    def get(self):
        item_key = self.request.get('item_key')
        item_search_word = self.request.get('item')

        self.response.write("""<html><HEAD><TITLE>アイテム 価格情報詳細</TITLE></HEAD><body>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<link media="only screen and (max-device-width:480px)" href="/stylesheets/sm.css" type="text/css" rel="stylesheet" />
<link media="screen and (min-device-width:481px)" href="/stylesheets/main.css" type="text/css" rel="stylesheet" />

<!--[if IE]>
<link href="/stylesheets/main.css" type="text/css" rel="stylesheet" />
<![endif]-->
    """)

        ua = self.request.headers['User-Agent']
        if re.match('Googlebot', ua) or re.match('wget', ua): # or Googlebot-Mobile
            self.response.write("To see details for item %s, use proper browser</BR></body></html>\n" % item_search_word )
            logging.info(ua)
            return

        # http://stackoverflow.com/questions/3869858/gql-query-with-numeric-id-in-datastore-viewer
        rec = mvc_itemtbl.get_by_key_name(item_key)
        if rec is None:
            self.response.write(u"エラー: %sに対するアイテム名が見つかりません。\n" % ( unicode(item_key) ) )
            self.response.write("</body></html>\n")
            return
        item_name = rec.name
        if len(item_name) == 0:
            self.response.write(u"エラー: %sに対するアイテム名が見つかりません。\n" % ( unicode(item_key) ) )
            self.response.write("</body></html>\n")
            return
        self.response.write("%s: " % (item_name) )

        self.response.write('<table class="tblbd">\n')
        self.response.write('<tr><td align=center>日付</td><td align=center>サーバ</td><td align=center>エリア</td><td align=center>最寄り</td><td align=center>番地</td><td align=center>X</td><td align=center>Z</td><td align=center>価格</td><td align=center>品名</td><td align=center>削除</td></tr>\n')

        hcnt = 0

        # http://d.hatena.ne.jp/shobonengine/20110628/1309252346
        for elm in rec.mvc_headtbl_set:
            hcnt += 1

            ddate =     elm.date
            Price_2 = re.sub(r'(\d)(?=(\d{3})+(?!\d))', '\\1,', str(elm.price))

            server1 =   0
            area1 =     0
            alter1 =    0
            adr1 =      0
            xc1 =       0
            zc1 =       0

            # 数値からエリア名などに変換する
            if elm.addr_ref is not None:
                server1 =   elm.addr_ref.server
                area1 =     elm.addr_ref.area
                alter1 =    elm.addr_ref.alter
                adr1 =      elm.addr_ref.address
                xc1 =       elm.addr_ref.xc
                zc1 =       elm.addr_ref.zc

                if server1 is None:
                    server1 = 0
                if area1 is None:
                    area1 = 0
                if adr1 is None:
                    adr1 = ""
                if alter1 is None:
                    alter1 = 0
                if xc1 is None:
                    xc1 = 0
                if zc1 is None:
                    zc1 = 0

            server2 = self.int_to_server(server1)
            area2 = self.int_to_area(area1)
            alter2 = self.int_to_alter(area1, alter1)

            self.response.write('<!-- server2: %s -->\n' % server2)
            self.response.write('<!-- area2: %s -->\n' % area2)
            self.response.write('<!-- alter2: %s (%d) -->\n' % (alter2, alter1) )

            self.response.write('<TR>\n')
            self.response.write('<td>%d/%02d/%02d</td><td>%s</td><td>%s</td><td align=center>%s</td><td><a href =%s>%s</a></td><td>%s</td><td>%s</td><td align=right>%s</td><td>%s</td>\n' % (
                ddate.year, ddate.month, ddate.day,
                server2,
                area2,
                alter2,
                self.generate_locator_link(server1, area1, adr1, xc1, zc1),
                adr1,
                xc1, zc1,
                Price_2,
                item_name ))
            self.response.write(u"""
<TD>
    <form action="/DeletePrice" method="post">
        <input type="hidden" name="key_name" value="%s" />
        <input type="submit" value="削除" />
    </form>
</TD>
""" % elm.key())
            self.response.write('</TR>\n')
        self.response.write('</table>\n')
        self.response.write("%d 件 ヒットしました。<BR>\n" % (hcnt) )

        # 「戻る」リンクの表示
        qf3 = re.sub('<', '&lt', item_search_word)
        qf3 = re.sub('>', '&gt', qf3)
        qf3 = qf3.encode('utf_8')
        self.response.write('<a href="/?item=%s">戻る(キーワード:%s)</a></BR>\n' % (qf3, qf3) )

        item_name2 = re.sub('<', '&lt', item_name)
        item_name2 = re.sub('>', '&gt', item_name2)
        item_name2 = item_name2.encode('utf_8')
        item_name = item_name.encode('utf_8')
        self.response.write( '<a href="/?item=&quot%s&quot">戻る(このアイテムで再検索:%s)</a>\n' % (item_name, item_name2) )

        self.response.write("</body></html>\n")
    
    def int_to_server(self, srv):
        if (srv < 0 or srv > 3):
            return "ERROR"
        resp = ["None", "EMERALD", "DIAMOND", "PEARL"]
        return resp[srv]

    def int_to_area(self, area):
        if (area < 0 or area > 6):
            return "ERROR"
        resp = [u"なし", u"ソレス", u"ユグ", u"ゲオ北東", u"ゲオ南", u"ゲオ西", u"シス"]
        return resp[area]

    def int_to_alter(self, area, alter):
        if (area < 0 or area > 6):
            return "ERROR"
        if (alter < 0 or alter > 15):
            return "ERROR"
        if (area == 6):
            #           0       1       2       3       4       5       6       7       8       9       10      11      12      13      14      15
            resp = [ u"メイン", "A-1",  "A-2",  "B-1",  "B-2",  "B-3",  "C-1",  "C-2",  "C-3",  "D-1",  "D-2",  "E-1",  "E-2",  "F-1",  "F-2",  "G-1" ]
            return resp[alter]
        if alter == 0:
            return u"メイン"
        return "%s" % alter    # そのまま。

    # MoE Locatorへのリンクを生成する(シスは未対応のため、自前で画像合成する。)
    def generate_locator_link(self, server, area, address, xc, zc):
        # http://m.syntacticsugar.net/?p;yug;9;4
        dmylink = "http://m.syntacticsugar.net/"
        if server is None:
            return dmylink
        if area is None:
            return dmylink
        if address is None:
            return dmylink
        if area == 6:
            return "/img/gen_map?xc=%d&zc=%d" % (xc, zc)
        if (area < 1 or area > 5):
            return dmylink
        if len(address) == 0:
            return dmylink
        ary_server = ["dummy", "e", "d", "p"]
        short_server = ary_server[server]
        ary_area = ["dummy", "thoress", "yug", "geoz-ne", "geoz-s", "geoz-w", "syth"]
        short_area = ary_area[area]
        adrr0 = int(address.split("-")[0])
        adrr1 = int(address.split("-")[1])
        return "http://m.syntacticsugar.net/?%s;%s;%d;%d" % (short_server, short_area, adrr0, adrr1)

class DeletePrice(webapp2.RequestHandler):
    def post(self):
        mykey = self.request.get('key_name')
        self.response.write("""<html><HEAD><TITLE>アイテム名修正</TITLE></HEAD><body>\n""")

        rec = mvc_headtbl.get(mykey)
        if rec is None:
            self.response.write(u"エラー: <B>%s</B>に対する価格情報が見つかりません。<BR>\n" % ( unicode(mykey) ) )
            self.response.write("</body></html>\n")
            return
        rec.delete()
        self.response.write(u"価格情報: %sを削除しました。<BR>\n"  % ( unicode(mykey) ) )
        self.response.write("</body></html>\n")

class Modify(webapp2.RequestHandler):
    @login_required
    def get(self):
        review = self.request.get('review')
        item_key = self.request.get('item_key')
        self.response.write("""<html><HEAD><TITLE>アイテム名修正</TITLE></HEAD><body>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<link media="only screen and (max-device-width:480px)" href="/stylesheets/sm.css" type="text/css" rel="stylesheet" />
<link media="screen and (min-device-width:481px)" href="/stylesheets/main.css" type="text/css" rel="stylesheet" />

<!--[if IE]>
<link href="/stylesheets/main.css" type="text/css" rel="stylesheet" />
<![endif]-->
    """)

        ua = self.request.headers['User-Agent']
        if re.match('Googlebot', ua) or re.match('wget', ua): # or Googlebot-Mobile
            self.response.write("To see details for item, use proper browser</BR></body></html>\n" )
            logging.info(ua)
            return

        rec = mvc_itemtbl.get_by_key_name(item_key)
        if rec is None:
            self.response.write(u"エラー: %sに対するアイテム名が見つかりません。\n" % ( unicode(item_key) ) )
            self.response.write("</body></html>\n")
            return

        qf1 = rec.name
        qf1 = qf1.encode('utf_8')
        qf3 = ''
        qf31 = rec.name_old
        if qf31 is not None:
            qf3 = qf31
            qf3 = qf3.encode('utf_8')
        else:
            qf3 = '(未設定)'
        if len(qf3) == 0:
            qf3 = '(なし)'
        item_key = item_key.encode('utf_8')
        self.response.write("""
    <form action="/PostModify" method="post">
      <table class="tblbd">
        <tr>
          <TD>
          現在のアイテム名: %s
          </TD>
          <TD> 新しいアイテム名 : <input type="text" name="item" value="%s" size="30" />
          </TD>
            <input type="hidden" name="item_key" value="%s" size="30" />
""" % (qf1, qf1, item_key) )

        if len(review) > 0:
            self.response.write("""
                <input type="hidden" name="review" value="%s" size="30" />
""" % review)

        self.response.write("""
          <TD><input type="submit" value="保存"></TD>
        </tr>
        <tr>
          <TD>
          (過去の修正: %s)
          </TD>
        </tr>
      </table>
    </form>""" % (qf3) )
        self.response.write("</body></html>\n")

class PostModify(webapp2.RequestHandler):
    def post(self):
        item = self.request.get('item')
        review = self.request.get('review')
        item_key = self.request.get('item_key')
        self.response.write("<html><HEAD><TITLE>アイテム名修正 POST</TITLE></HEAD><body>")
        rec = mvc_itemtbl.get_by_key_name(item_key)
        if rec is None:
            self.response.write(u"エラー: %sに対するアイテム名が見つかりません。\n" % ( unicode(item_key) ) )
            self.response.write("</body></html>\n")
            return

        # バックアップを取る
        rec.name_old = rec.name
        bak = mvc_itembak(name=item, name_old=rec.name_old, author=users.get_current_user(), ip=self.request.remote_addr)
        bak.put()
        url =''
        # 重複する同盟のエンティティがあれば、マージする。
        if self.merge_if_exist(rec,item) > 0:
            rec.delete()
            if len(review) > 0:
                url = '/Review?review=%s&' % (review)
            else:
                url = '/?'
        else:
            rec.name = item
            # 編集者の設定
            # https://developers.google.com/appengine/docs/python/gettingstarted/usingdatastore?hl=ja
            rec.author = users.get_current_user()
            rec.review = 10 # レビュー待ち(1) 状態
            rec.put()
            self.response.write("</body></html>\n")
            if len(review) > 0:
                url = '/Review?review=%s&' % (review)
            else:
                url = '/Modify?item_key=%s' % (item_key)
        self.redirect(url)
        return

    def merge_if_exist(self, old_ent, new_name):
        #logging.getLogger().setLevel(logging.DEBUG)
        entities = db.GqlQuery(u"SELECT * FROM mvc_itemtbl WHERE name = '%s' LIMIT 2" % (new_name) )
        ent_cnt = entities.count()
        logging.info("new_name:%s ent_cnt: %d\n" % (new_name, ent_cnt) )
        if ent_cnt < 1:
            return 0    # 重複なし

        merge_dest = None
        for ent in entities:
            if ent.key() != old_ent.key():
                merge_dest = ent
                break
        if merge_dest is None:
            logging.info("Same named entity was not found.")
            return 0

        # 参照を既存のものに変更する
        hdr_lst = []
        for hdr in old_ent.mvc_headtbl_set:
            if self.is_same_hdr_exist(hdr, merge_dest) == 1:
                continue
            hdr.item_ref = merge_dest
            hdr_lst.append(hdr)
        if len(hdr_lst) > 0:
            db.put(hdr_lst)
        return ent_cnt

    def is_same_hdr_exist(self, hdr, merge_dest):
        for merge_hdr in merge_dest.mvc_headtbl_set:
            if hdr.price == merge_hdr.price:
                if hdr.addr_ref.server == merge_hdr.addr_ref.server and hdr.addr_ref.area == merge_hdr.addr_ref.area and hdr.addr_ref.alter == merge_hdr.addr_ref.alter:
                    return 1
        return 0


class Review(webapp2.RequestHandler):
    def get(self):
        per_page = 100
        max_page = 3
        max_read = per_page * max_page
        unlim = self.request.get('unlim')
        if unlim:
            per_page = 1000
        qpage = self.request.get('page')
        ipage = str2int(qpage)

        self.response.write("""<html><body>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<link media="only screen and (max-device-width:480px)" href="/stylesheets/sm.css" type="text/css" rel="stylesheet" />
<link media="screen and (min-device-width:481px)" href="/stylesheets/main.css" type="text/css" rel="stylesheet" />

<!--[if IE]>
<link href="/stylesheets/main.css" type="text/css" rel="stylesheet" />
<![endif]-->

        """)
        form_review = self.request.get('item')
        qitem = self.request.get('item')
        self.response.write("<!-- item:%s-->\n" % qitem)
        mdq = re.search('"', qitem)
        if mdq is not None:
            qitem = re.sub('"', '&quot', qitem)
        qitem_key = re.sub('&quot', '', qitem)

        form_review = self.request.get('review')
        review = 0
        if len(form_review) > 0:
            review = str2int(form_review)
        self.response.write("<!-- review:%d -->\n" % review)
        rv0 = ''
        rv10 = ''
        rv20 = ''
        if review == 0:
            rv0 = 'checked'
        if review == 10:
            rv10 = 'checked'
        if review == 20:
            rv20 = 'checked'
        # Write the submission form and the footer of the page
        self.response.write(u"""
    <form action="/Review" method="get">
      <table class="tblbd">
        <tr>
          <TD> アイテム名 :<BR> 
            <input type="text" name="item" value="%s" size="30" /><BR>
    """  % (qitem) )

    #        self.response.write("""
    #<input type="hidden" name="review" value="%d" />
    #""" % (review) )

        self.response.write("""
    <BR>
    
    <input type="radio" name="review" value="0" %s /> 未指定
    """ % (rv0) )
        self.response.write("""
    <input type="radio" name="review" value="10" %s /> 修正済/レビュー待
    """ % (rv10) )
        self.response.write("""
    <input type="radio" name="review" value="20" %s /> レビュー完了
    """ % (rv20) )
            
        self.response.write("""
            <input type="submit" value="検索"></TD>
        </tr>
      </table>
    </form>\n""" )
        records = None
        rec_cnt = 0
        ttl_page = 0
        rlist = []
        if len(qitem_key) > 0:
            # ダブルクオートで囲ってある場合は、一致チェックする。
            if mdq is not None:
                sql1 = u"SELECT * FROM mvc_itemtbl WHERE name = '%s' AND review = %d" % (qitem_key, review)
                sql1 += " LIMIT %d" % per_page
                if review > 0:
                    sql1 += " OFFSET %d" % ipage * per_page
                records = db.GqlQuery(sql1)
                rec_cnt = records.count()
                self.response.write("<!-- Query1:%d -->\n" % rec_cnt)
            if rec_cnt == 0:
                # アイテムテーブルから指定アイテム名を検索する
                records = mvc_itemtbl.all().filter('review = ', review).search(qitem_key).fetch(limit=per_page, offset=ipage*per_page)
                rec_cnt = len(records)
                if rec_cnt > 0:
                    self.response.write("<!-- Query2:%s -->\n" % rec_cnt )
            rlist.append(records)
        if rec_cnt == 2240:
            # 指定がない場合は、pageに対応した位置のレコードを全部取得する。
            end_cursor = None
            for idx in range(0, max_page):
                found = 0
                ofs = (ipage + idx) * per_page

                q = mvc_itemtbl.all()               # 全部取得
                q.filter('review = ', review)       # 絞り込み
                # 前回のカーソルが登録されていれば取得する
                if end_cursor:
                    # 次に続くページが有るか確認: 最小限のページを得る
                    records = q.with_cursor(start_cursor=end_cursor).fetch(limit=1, offset=ofs)
                    found = q.with_cursor(start_cursor=end_cursor).count(1)
                else:
                    records = q.fetch(limit=per_page, offset=ofs)
                    found = len(records)
                    # 最初だけ、現在のページ分として後で表示するためリストに追加
                    rlist.append(records)
                    rec_cnt += found

                end_cursor = q.cursor()             # 終了位置保存
                if end_cursor is None:
                    self.response.write("<!-- Query5 end -->\n")
                    break

                self.response.write("<!-- Query5: Page %02d: limit:%d offset:%d found:%d -->\n" % (idx + ipage, per_page, ofs, found) )
                if found == 0:
                    break
                ttl_page += 1
        if rec_cnt == 0:
            # 全検索する
            sql1 = u"SELECT * FROM mvc_itemtbl WHERE review = %d " % (review)
            #sql1 += " OFFSET %d LIMIT %d" % ( ipage*per_page, per_page )
            ofs = ipage*per_page
            records_all = db.GqlQuery(sql1) #.fetch( limit=per_page, offset=ofs )
            records = records_all.fetch(limit=per_page, offset=ofs )
            rec_cnt = len(records)
            self.response.write("<!-- Query3: sql1: %s limit %d offset %d -->\n" % (sql1, per_page, ofs) )
            self.response.write("<!-- Query3:%d -->\n" % rec_cnt)
            rlist.append(records)
        if rec_cnt == 0:
            self.response.write('一致なし\n')
            self.response.write("</body></html>\n")
            return

        # 表の表示開始
        self.response.write('<table class="tblbd">\n')
        self.response.write('<tr><td align=center>品名</td><td align=center>詳細</td><td align=center>&nbsp;</td><td align=center>削除</td>\n')
        self.response.write('</tr>\n')
        cnt = 0
        for rr in rlist:
            for elm in rr:
                name1 = elm.name
                if name1 is None:
                    name1 = (u'名称未設定 %04d' % cnt)

                name1 = re.sub('<', '&lt', name1)
                name1 = re.sub('>', '&gt', name1)

                name1 = name1.encode('utf_8')
                item_key = elm.key().name()
                self.response.write('<tr>\n')
                self.response.write('<td>%s</td>\n' % (name1) )
                self.response.write('<td><a href="/Detail?item_key=%s&">\n' % item_key )
                self.response.write('表&nbsp;示</a></td>\n')
                self.response.write('<TD><a href="/Modify?item_key=%s&review=%d">' % (item_key, review) )
                self.response.write('修&nbsp;正</a></td>\n')
                self.response.write(u"""
<TD>
    <form action="/DeleteItemConfirm" method="post">
        <input type="hidden" name="item_key" value="%s" />
        <input type="submit" value="削除" />
    </form>
</TD>
""" % item_key)
                self.response.write('</tr>\n')
                cnt += 1

            if rr is None:
                break
            if len(rr) == 0:
                break
            self.response.write("<!-- <tr><td> ---------- kugiri rr:%d ---------- </td></tr> -->\n" % cnt)

        self.response.write('</table>\n')   #No2
        self.response.write('%d件のアイテムがヒットしました。<BR>\n' %(cnt) )
        self.response.write('</BR>\n')
        self.response.write('<table class="tblbd">\n')
        self.response.write('<tr>\n')
        self.response.write('  <td align=center>ﾚﾋﾞｭｰ</td>\n')
        self.response.write('  <td align=center>修正候補</td>\n')
        self.response.write('</tr>\n')
        self.response.write(u"""
    <form action="/ReviewCmpl" method="post">
    """)
        cnt = 0
        for rr in rlist:
            for elm in rr:
                self.response.write('<tr>\n')
                self.response.write(u"""
        <TD align = center>
          <input type="checkbox" name="item_key" value="%s" />
          <input type="hidden" name="review" value="20" />
          <input type="hidden" name="review_prev" value="%d" />
        </TD>""" % ( elm.key().name(), review ) )
                name2 = elm.name
                if name2 is None:
                    name2 = u"名称未設定 %04d" % cnt
                name2 = name2.encode('utf_8')
                self.response.write('<td>%s = ' % (elm.name_old) )
                self.response.write( name2 )
                self.response.write('</td>\n')
                self.response.write('</tr>\n')
                self.response.write('<TD></TD>\n')
                cnt += 1
            if rr is None:
                break
            if len(rr) == 0:
                break
            self.response.write("<!-- <tr><td> ---------- kugiri rr:%d ---------- </td></tr> -->\n" % cnt)

        self.response.write('<TD>\n')
        self.response.write('<input type="submit" value="完了！">\n')
        self.response.write('</form>\n')
        self.response.write('</TD>\n')
        self.response.write('</table>\n') # No2
        self.response.write('%s\n' % '</BR>' * 10)
        self.response.write("</body></html>\n")
    def get_ttl_page(self):
        # 総ページ数を得る
        rec_cnt = 0
        start_cursor = None
        end_cursor = None
        for i in range(0, 10):
            #q = q.filter('review = ', review)
            #q = q.filter('review = ', None)
            # 前回のカーソルが登録されていれば取得する
            if end_cursor:
                q = mvc_itemtbl.all().with_cursor(start_cursor=end_cursor)
                records1 = q.fetch(limit=1000)
                start_cursor = q.cursor()
                records = q.fetch(limit=1, offset=1000)
                end_cursor = q.cursor()
            else:
                q = mvc_itemtbl.all()
                records1 = q.fetch(limit=1000)
                start_cursor = q.cursor()
                records = q.fetch(limit=1, offset=1000)
                end_cursor = q.cursor()
            if end_cursor is None:
                self.response.write("<!-- Query4: end (break by: cursor is none) -->\n")
                break
            count_at_cursor = len(records1)
            self.response.write("<!-- Query4: Page: %02d ttl:%d (now +%d) -->\n" % ( i, rec_cnt, count_at_cursor) )
            if count_at_cursor < 1000:
                records = q.with_cursor(start_cursor=start_cursor).fetch(limit=1000)
                count_at_cursor = len(records1)
                rec_cnt += count_at_cursor
                self.response.write("<!-- Query4: Page: %02d ttl:%d (now +%d) break by no more records. -->\n" % ( i, rec_cnt, count_at_cursor) )
                break
            rec_cnt += 1000 # 1000件以上あった..？ : 続行！
        self.response.write("Total %d records.<BR>\n" % rec_cnt)
        return
class ReviewCmpl(webapp2.RequestHandler):
    def post(self):
        # 複数の値を受け取る
        # http://www.jinlingren.com/eid96.html
        keys_ = self.request.get_all('item_key')
        review = self.request.get('review_prev')
        self.response.write("<html><HEAD><TITLE>アイテム名修正 POST</TITLE></HEAD><body>")
        for item_key in keys_:
            rec = mvc_itemtbl.get_by_key_name(item_key)
            if rec is None:
                self.response.write(u"エラー: %sに対するアイテム名が見つかりません。\n" % ( unicode(item_key) ) )
                self.response.write("</body></html>\n")
                continue
            rec.review = 20 # レビュー完了(2) 状態
            rec.put()
            self.response.write(u"%s : レビュー完了<BR>\n" % rec.name)
        self.response.write("</body></html>\n")
        self.redirect('/Review?review=%d&' % str2int(review))

class DeleteItem(webapp2.RequestHandler):
    def post(self):
        self.response.write("<html><HEAD><TITLE>アイテム名 削除</TITLE></HEAD><body>")

        item_key = self.request.get('item_key')
        # キー名からエンティティ取得
        rec = mvc_itemtbl.get_by_key_name(item_key)
        if rec is None:
            self.response.write(u"エラー: %sに対するアイテム名が見つかりません。\n" % ( unicode(item_key) ) )
            self.response.write("</body></html>\n")
            return
        # 子を検索
        for child in rec.mvc_headtbl_set:
            self.response.write(u"価格情報: %d : 削除完了<BR>\n" % child.price)
            child.delete()

        # 親を削除
        bak = mvc_itemtrash(name=rec.name, name_old=rec.name_old, author=users.get_current_user(), ip=self.request.remote_addr)
        bak.put()
        rec.delete()
        self.response.write(u"アイテム名: %s : 削除完了<BR>\n" % rec.name)
        memcache.delete(rec.name)
        self.response.write("</body></html>\n")

class DeleteItemConfirm(webapp2.RequestHandler):
    def post(self):
        self.response.write("<html><HEAD><TITLE>アイテム名 削除確認</TITLE></HEAD><body>")

        ua = self.request.headers['User-Agent']
        if re.match('Googlebot', ua) or re.match('wget', ua): # or Googlebot-Mobile
            self.response.write("To see details for item, use proper browser</BR></body></html>\n" )
            logging.info(ua)
            return

        item_key = self.request.get('item_key')
        # キー名からエンティティ取得
        rec = mvc_itemtbl.get_by_key_name(item_key)
        if rec is None:
            self.response.write(u"エラー: %sに対するアイテム名が見つかりません。\n" % ( unicode(item_key) ) )
            self.response.write("</body></html>\n")
            return

        # 子の件数を求める
        cnt = 0
        for child in rec.mvc_headtbl_set:
            cnt += 1
        self.response.write(u"従属する価格情報 %d件 も全て削除されます。よろしいですか？<BR>\n" % cnt )
#       self.response.write(u'<a href="/DeleteItem?item_key=%s">続行</A><BR>\n' % (item_key) )

        self.response.write(u"""
<TD>
    <form action="/DeleteItem" method="post">
        <input type="hidden" name="item_key" value="%s" />
        <input type="submit" value="続行" />
    </form>
</TD>
""" % item_key )

        self.response.write("</body></html>\n")

# for debug
class PostItem(webapp2.RequestHandler):
    def get(self):
        self.response.write('<html><body>')

        # Write the submission form and the footer of the page
        self.response.write("""
      <form action="/PostDebug" method="post">
        <div><input name="server" rows="1" cols="10"></div>
        <div><input name="area" rows="1" cols="30"></div>
        <div><input name="alter" rows="1" cols="10"></div>
        <div><input name="address" rows="1" cols="10"></div>
        <div><input name="xc" rows="1" cols="10"></div>
        <div><input name="zc" rows="1" cols="10"></div>
        <div><input name="price" rows="1" cols="10"></div>
        <div><input name="item" rows="2" cols="60"></div>
        <div><input type="submit" value="Post item"></div>
      </form>
    </body>
    </html>""")

class PostDebug(webapp2.RequestHandler):
    def post(self):

        self.response.write('<HTML><HEAD><TITLE>デバッグ用データ登録</TITLE></HEAD><body>')
        server = self.request.get('server')
        area = self.request.get('area')
        alter = self.request.get('alter')
        address = self.request.get('address')
        xc = self.request.get('xc')
        zc = self.request.get('zc')

        ppr = self.request.get('price')
        iprice = str2int(ppr)

        item = self.request.get('item')
        tick = "tick_test_12345"

        items = mvc_itemtbl.get_or_insert(key_name=tick, name=item)
        if items is None:
            items = mvc_itemtbl(key_name=tick)
            items.name = item
            items.put()
            self.response.write('<B>PUT OK</B><BR>')
        self.response.write('items: %s<BR>' % (items ) )

        if items is None:
            self.response.write('Error: No items' )
            return
        obj = mvc_headtbl()
        obj.server = str2int(server)
        obj.area = str2int(area)
        obj.alter = str2int(alter)
        obj.address = address
        obj.xc = str2fl(xc)
        obj.zc = str2fl(zc)
        obj.price = iprice
        obj.item_ref = items
        obj.put()

        self.response.write('MVC_Insert_to_DB_OK<BR>')
        self.response.write(' respo: %s<BR>' % (self.request))
        self.response.write(' server: %s<BR>' % (server))
        self.response.write(' area: %s<BR>' % (area))
        self.response.write(' alter: %s<BR>' % (alter))
        self.response.write(' address: %s<BR>' % (address))
        self.response.write(' xc: %s<BR>' % (xc))
        self.response.write(' zc: %s<BR>' % (zc))
        self.response.write(' price: %d<BR>' % (iprice))
        self.response.write(' item: %s(KEY is %s)<BR>' % (item, tick))

class PostAHK(webapp2.RequestHandler):
    def post(self):
        records = ""
        for i in range(1, 16):
            try:
                recc = self.request.get('rec%d' % i)
                if recc is None:
                    break
                records += recc + "\n"
                continue
            except UnicodeDecodeError:
                logging.error('[%02d] UnicodeDecodeError(1)' % i)
            try:
                recc = unicode(self.request.get('rec%d' % i), errors='ignore')
                if recc is None:
                    break
                records += recc + "\n"
            except UnicodeDecodeError:
                logging.error('[%02d] UnicodeDecodeError(2)' % i)

        taskqueue.add(
            url='/PostAHK_Worker',
            params={'server': self.request.get('server'),
                'area': self.request.get('area'),
                'alter': self.request.get('alter'),
                'address': self.request.get('address'),
                'xc': self.request.get('xc'),
                'zc': self.request.get('zc'),
                'records': records,
                'tick': self.request.get('tick'),
            }
        )
        self.response.write('MVC_Insert_to_DB_OK\n')
        self.response.write('Task was added.\n')
        return

class PostAHK_Worker(webapp2.RequestHandler):

    def pick_already_append(self, item_name, entity_list):
        for prev in entity_list:
            if prev.name == item_name:
                return prev
        return None

    # nil:      番地情報なし
    # nil以外:  番地情報あり, 番地が一致
    #           番地情報あり, 番地が一致しなかったがアルターまで一致
    def addr_tbl_get_or_insert(self, iserver, iarea, ialter, ixc, izc, address):
        # 同アルターの番地情報テーブルがすでにあるか検索する
        # (最優先 ... 座標有り=番地有り の情報を検索する)
        if ixc > 0 and izc > 0:
            addr_ent = mvc_addrtbl.all().filter('server =', iserver).filter('area =', iarea).filter('alter =', ialter).filter('address =', address).get()
            if addr_ent is not None:
                # 商品情報・番地情報・価格情報すべて両方一致したので、番地情報の追加はしない。
                # (更に番地情報にX,Z座標があった→座標なしのエンティティより優先する)
                return addr_ent
        else:
            # 座標がない場合は、アルターまで一致チェックする
            addr_ent = mvc_addrtbl.all().filter('server =', iserver).filter('area =', iarea).filter('alter =', ialter).get()
            if addr_ent is not None:
                # 商品情報・番地情報・価格情報すべて両方一致したので、番地情報の追加はしない。
                return addr_ent

        # 番地情報テーブルが見つからなかったため、新規エンティティを作成する。
        addr_rec = mvc_addrtbl(server=iserver, area=iarea, alter=ialter, address=address, xc=ixc, zc=izc)
        db.put(addr_rec)
        logging.info( 'Appended address %s table' % (address) )
        return addr_rec

    # nil:      番地情報なし
    # nil以外:  番地情報あり, 価格と商品名も一致
    def hdr_tbl_get(self, iserver, iarea, ialter, iprice, ent_item, ixc, izc, address):
        # 同アルターの番地情報テーブルがすでにあるか検索する
        # (最優先 ... 座標有り=番地有り の情報を検索する)
        if ixc > 0 and izc > 0:
            addr_rec = mvc_addrtbl.all().filter('server =', iserver).filter('area =', iarea).filter('alter =', ialter).filter('address =', address).get()
            if addr_rec is not None:
                hdr = addr_rec.mvc_headtbl_set.filter('price =', iprice).filter('item_ref =', ent_item.key()).get()
                if hdr is not None:
                    # 商品情報・番地情報(番地)・価格情報すべて一致したので、価格情報の追加はしない。
                    # (更に番地情報にX,Z座標があった→座標なしのエンティティより優先する)
                    return hdr

        # 同アルターの番地情報テーブルがすでにあるか検索する
        addr_rec = mvc_addrtbl.all().filter('server =', iserver).filter('area =', iarea).filter('alter =', ialter).get()
        if addr_rec is None:
            return None
        # 価格・商品名が一致するテーブルが有るか検索する
        hdr = addr_rec.mvc_headtbl_set.filter('price =', iprice).filter('item_ref =', ent_item.key()).get()
        if hdr is not None:
            # 商品情報・番地情報(番地は違うかもしれないがアルターが一致)・価格情報すべて両方一致したので、番地情報の追加はしない。
            return hdr

        return None

    def post(self):
        # GAE/Pタスクキューで実行頻度の設定と、無限リトライ防止(ローカルでは動かない)
        # http://linux.oboe-gaki.com/archives/000364.html
        try:
            if( int(self.request.headers.environ['HTTP_X_APPENGINE_TASKRETRYCOUNT']) > 3):
                logging.error('The job retry error(over 3 times)')
                return
        except:
            logging.info('HTTP_X_APPENGINE_TASKRETRYCOUNT is not implemented!')
        tick = self.request.get('tick')
        server = self.request.get('server')
        area = self.request.get('area')
        alter = self.request.get('alter')
        address = self.request.get('address')
        xc = self.request.get('xc')
        zc = self.request.get('zc')
        ixc = str2fl(xc)
        izc = str2fl(zc)

        records = self.request.get('records')

        iserver = str2int(server)
        iarea = str2int(area)
        ialter = str2int(alter)

        # 番地情報のアドレスを追加する
        addr_rec = self.addr_tbl_get_or_insert(iserver, iarea, ialter, ixc, izc, address)
        if addr_rec is None:
            logging.error( 'Failed to get address %s table' % (address) )
        addr_cache_append(iserver, iarea, ialter)

        cnt = 0
        tcnt = 0
        hdr_list = []
        added_name_lst = []
        added_entity_list = []
        rect = records.split('\n')
        # 名前のダンプ
        #for rec in rect:
        #    if len(rec) < 3:
        #        break
        #    logging.info(rec)
        for rec in rect:
            cnt += 1
            if len(rec) < 3:
                continue
            tick2 = "%s-%02d" % (tick, cnt)
            price = rec.split('\t')[0]
            iprice = str2int(price)

            item = rec.split('\t')[1]
            if item is None:
                item = price
            if item is None:
                continue
            item = item.strip()

            # アイテムをアイテムテーブルから検索
            ent_item = name_cache_get(item)
            if ent_item is None:
                # キャッシュミス: クエリ実行
                sql1 = "SELECT * FROM mvc_itemtbl WHERE name = '%s' " % (item)
                items = db.GqlQuery(sql1)
                for elm in items:       # 先頭のエンティティのみ持ってくる
                    ent_item = elm
                    name_cache_append(item, ent_item)
                    break

            # 既にこのトランザクションで追加していないか？
            if ent_item is None:
                ent_item = self.pick_already_append(item, added_entity_list)
                #logging.info('[%02d] picked prev data' % cnt)

            # アイテムが見つからなかった... テーブル追加。
            if ent_item is None:
                ent_item = mvc_itemtbl.get_or_insert(key_name=tick2, name=item)
                logging.info('[%02d] The item entity for %s was added.' % (cnt, item) )
                added_entity_list.append(ent_item)
                name_cache_append(item, ent_item)

            # 既にこのトランザクションで追加した同名のエンティティがないか探す
            # 注意事項: 同名のアイテムで価格が違う場合など; 例えばMGとHGの違い は無視される。)
            if item in added_name_lst:
                #logging.info('[%02d] skip adding(route 2)' % cnt)
                continue    # 同一トランザクションならば、スキップする。

            hdr = self.hdr_tbl_get(iserver, iarea, ialter, iprice, ent_item, ixc, izc, address)
            # 番地情報も価格も一致したので、何もしない。
            if hdr is not None:
                #logging.info('[%02d] skip adding(route 3)' % cnt)
                continue

            # 既存の価格情報テーブルがなかった場合、追加する。
            hdr = mvc_headtbl(price = iprice, item_ref=ent_item, addr_ref=addr_rec)
            hdr_list.append(hdr)
            added_name_lst.append(item)
            tcnt += 1
            logging.info('[%02d] Appended price table of %s (%d)' % (cnt, item, tcnt))

        db.put(hdr_list)  # 書き込み

class Wipe(webapp2.RequestHandler):
    @login_required
    def get(self):
        #self.unreview()
        #self.fill(800)
        #self.clear_e()

#        self.correct_hiden(u"秘伝の書")
  #      self.correct_hiden(u"魔獣の古文書")
  #      self.correct_hiden(u"速斬の古文書")
  #      self.correct_hiden(u"雷光の古文書")
  #      self.correct_hiden(u"魔翼の古文書")
  #      self.correct_hiden(u"精霊の古文書")
  #      self.correct_hiden(u"混沌の古文書")
  #      self.correct_hiden(u"錬成の古文書")
#        self.correct_hiden(u"ノアピース")
#        self.correct_hiden(u"究極奥儀の書")

        #self.item_correct_space()

        #self.head_remove_all_itemid()
        #self.head_copy_addr()
        #self.del_attrs()
        #self.test_head1()

        self.wipe_header()
        return

    def correct_hiden(self, text):
        # アイテムテーブルから指定アイテム名を検索する
        # 魔獣の古文書
        # ノアピース

        records = mvc_itemtbl.all().search(text).fetch(limit=1000)
        dlst = []
        self.response.write("Read %d entries</BR>\n" % len(records) )
        for rec in records:
            orig = rec.name
            self.response.write( orig )
            self.response.write( " : " )
            chg = re.sub("\[", " [", orig)
            self.response.write( chg )
            self.response.write( "</BR>\n" )
            rec.name = chg
            dlst.append(rec)
        self.response.write("Wrote %d entries</BR>\n" % len(dlst) )
        db.put(dlst)
        return

    # 無駄に重なっていたスペースを一つにまとめる
    def item_correct_space(self):
        next_cursor = None
        self.response.write('<table>')
        for i in range(1,100):
            if next_cursor is None:
                q = mvc_itemtbl.all()
            else:
                q = mvc_itemtbl.all().with_cursor(start_cursor=next_cursor)
            records = q.fetch(limit=1000)
            next_cursor = q.cursor()             # 今の終了位置 → 次の開始位置
            if len(records) == 0:
                continue
            dlst = []
            self.response.write( "<TR>" )
            self.response.write( "<TD>" )
            self.response.write("Read %d entries</BR>\n" % len(records) )
            self.response.write( "</TD>" )
            self.response.write( "</TR>" )
            for rec in records:
                orig = rec.name

                chg = orig.strip()
                #chg = re.sub("[ ]+", " ", orig)
                #chg = re.sub(u'ートール', u'ー トール', chg)
                #chg = re.sub(u'ードワーヴン', u'ー ドワーヴン', chg)
                #chg = re.sub(u'ードレス', u'ー ドレス', chg)
                #chg  = re.sub(u'卜', u'ト', orig)
                #chg  = re.sub(u'ー ト', u'ート', chg)
                #chg  = re.sub(u'ッ ト', u'ット', chg)
                #chg  = re.sub(u'シャウ ト', u'シャウト', chg)
                #chg  = re.sub(u'スカウ ト', u'スカウト', chg)
                #chg  = re.sub(u'トレン ト', u'トレント', chg)
                #chg  = re.sub(u'ボス トン', u'ボストン', chg)
                #chg  = re.sub(u'ホワイ ト', u'ホワイト', chg)
                #chg  = re.sub(u'ダス ト', u'ダスト', chg)
                #chg  = re.sub(u'ナイ ト', u'ナイト', chg)
                #chg  = re.sub(u'ス トリング', u'ストリング', chg)
                #chg  = re.sub(u'ヒ トニナール', u'ヒトニナール', chg)
                #chg  = re.sub(u'チェス ト', u'チェスト', chg)
                #chg  = re.sub(u'ヤマ ト', u'ヤマト', chg)
                #chg  = re.sub(u'ミ トン', u'ミトン', chg)
                #chg  = re.sub(u'ス トーン', u'ストーン', chg)
                #chg  = re.sub(u'ヴァリアン ト', u'ヴァリアント', chg)
                #chg  = re.sub(u'バ〜ク', u'バック', chg)
                #chg  = re.sub(u'ノスフェラ トゥ', u'ノスフェラトゥ', chg)
                #chg  = re.sub(u'オーバードライブ', u'オーバー ドライブ', chg)

                #chg  = re.sub(u'ー ド', u'ード', chg)
                chg  = re.sub(u'ッ ド', u'ッド', chg)
                chg  = re.sub(u'シール ド', u'シールド', chg)
                chg  = re.sub(u'サイ ド', u'サイド', chg)
                chg  = re.sub(u'ブレイ ド', u'ブレイド', chg)
                chg  = re.sub(u'バン ド', u'バンド', chg)
                if orig == chg:
                    continue
                self.response.write( "<TR>" )
                self.response.write( "<TD>" )
                self.response.write( orig )
                self.response.write( "</TD>" )
                self.response.write( "<TD>" )
                self.response.write( chg )
                self.response.write( "</TD>" )
                self.response.write( "</TR>" )
                rec.name = chg
                rec.review = 0
                dlst.append(rec)
            if len(dlst) > 0:
                db.put(dlst)
            self.response.write( "<TR>" )
            self.response.write( "<TD>" )
            self.response.write("Wrote %d entries</BR>\n" % len(dlst) )
            self.response.write( "</TD>" )
            self.response.write( "</TR>" )
        self.response.write('<table>')
        return

    def head_remove_all_itemid(self):
        next_cursor = None
        for i in range(1, 100):
            if next_cursor is None:
                q = mvc_headtbl.all()
            else:
                q = mvc_headtbl.all().with_cursor(start_cursor=next_cursor)
            records = q.fetch(limit=1000)
            next_cursor = q.cursor()             # 今の終了位置 → 次の開始位置
            if len(records) == 0:
                continue

            dlst = []
            for rec in records:
                rec.item_id = None
                dlst.append(rec)
            if len(dlst) > 0:
                db.put(dlst)
            self.response.write("Wrote %d entries</BR>\n" % len(dlst) )
        return

    def test_head1(self):
        lst = mvc_headtbl.all().filter('area =', 5)
        self.response.write("<table border=1>\n")
        for aa in lst:
            self.response.write("<tr>\n")
            self.response.write("  <td>%d</td>\n" % aa.server)
            self.response.write("  <td>%d</td>\n" % aa.area)
            self.response.write("  <td>%s</td>\n" % aa.address)
            self.response.write("  <td>%d</td>\n" % aa.xc)
            self.response.write("  <td>%d</td>\n" % aa.zc)
            self.response.write("</tr>\n")
        self.response.write("</table></BR></BR></BR>\n")

    def head_copy_addr1(self):
        next_cursor = None
        exist_addr_lst = mvc_addrtbl.all().fetch(limit=1000)
        self.response.write("registered %d tables.</BR>\n" % len(exist_addr_lst) )
    #   self.response.write("<table border=1>\n")
    #   for aa in exist_addr_lst:
    #       self.response.write("<tr>\n")
    #       self.response.write("  <td>%d</td>\n" % aa.server)
    #       self.response.write("  <td>%d</td>\n" % aa.area)
    #       self.response.write("  <td>%s</td>\n" % aa.address)
    #       self.response.write("  <td>%d</td>\n" % aa.xc)
    #       self.response.write("  <td>%d</td>\n" % aa.zc)
    #       self.response.write("</tr>\n")
    #   self.response.write("</table></BR></BR></BR>\n")

        str_area = self.request.get('area')
        str_max = self.request.get('max')
        ii_area = str2int(str_area)
        ii_max = str2int(str_max)

        hlst = []
        dlst = []
        for i in range(0, ii_max):
            self.response.write("[mvc_headtbl] process:%d</BR>\n" % i )
            if next_cursor is None:
                q = mvc_headtbl.all().filter('area =', ii_area)
                #q = mvc_headtbl.all()
            else:
                q = mvc_headtbl.all().filter('area =', ii_area).with_cursor(start_cursor=next_cursor)
                #q = mvc_headtbl.all().with_cursor(start_cursor=next_cursor)
            records = q.fetch(limit=1000)
            next_cursor = q.cursor()             # 今の終了位置 → 次の開始位置
            if len(records) == 0:
                continue

            for rec in records:
                if rec.addr_ref is not None:
                    rec.area = 99
                    hlst.append(rec)
                    continue

                found = 0
                # 既存のテーブル検索
                for aa in exist_addr_lst:
                    if aa.server == rec.server and aa.area == rec.area and aa.address == rec.address:
                        found = 1
                        rec.addr_ref = aa   # 既存のアドレステーブルに見つかったら、参照する。
                        hlst.append(rec)
                        break
                if found == 1:
                    continue
                # これから追加するメモリ内検索
                for aa in dlst:
                    if aa.server == rec.server and aa.area == rec.area and aa.address == rec.address:
                        found = 1
                        break
                if found == 1:
                    continue
                # 既存のテーブルにもメモリにも見つからなかった→ テーブル追加
                #self.response.write("%s</BR>\n" % rec.date )
                addr = mvc_addrtbl(server=rec.server, area=rec.area, address=rec.address, alter=rec.alter, xc=rec.xc, zc=rec.zc)
                dlst.append(addr)

        self.response.write("[mvc_addrtbl] Read %d entries</BR>\n" % len(dlst) )
        db.put(dlst)
        self.response.write("<table border=1>\n")
  #     for aa in dlst:
  #         self.response.write("<tr>\n")
  #         self.response.write("  <td>%d</td>\n" % aa.server)
  #         self.response.write("  <td>%d</td>\n" % aa.area)
  #         self.response.write("  <td>%s</td>\n" % aa.address)
  #         self.response.write("  <td>%d</td>\n" % aa.xc)
  #         self.response.write("  <td>%d</td>\n" % aa.zc)
  #         self.response.write("</tr>\n")
  #     self.response.write("</table>\n")

        self.response.write("[mvc_headtbl] Read %d entries</BR>\n" % len(hlst) )
        db.put(hlst)
        return

    def head_copy_addr(self):

        str_max = self.request.get('max')
        ii_max = str2int(str_max)
        if ii_max == 0:
            ii_max = 20

        hdr_lst = []
        addr_lst = []

        q = mvc_headtbl.all().filter('area <', 99)
        records = q.fetch(limit=ii_max)
        if len(records) == 0:
            return

        exist_addr_lst = mvc_addrtbl.all().fetch(limit=1000)
        self.response.write("registered %d tables.</BR>\n" % len(exist_addr_lst) )
        for rec in records:

            found = 0
            self.response.write("Wrote: %s</BR>\n" % (rec.date) )
            # 既存のテーブル検索
            for aa in exist_addr_lst:
                if aa.server == rec.server and aa.area == rec.area and aa.address == rec.address:
                    found = 1
                    rec.addr_ref = aa   # 既存のアドレステーブルに見つかったら、参照する。
                    rec.area = 99
                    hdr_lst.append(rec)
                    break
            if found == 1:
                continue
            # これから追加するメモリ内検索
            for aa in addr_lst:
                if aa.server == rec.server and aa.area == rec.area and aa.address == rec.address:
                    found = 1
                    break
            if found == 1:
                continue
            # 既存のテーブルにもメモリにも見つからなかった→ テーブル追加
            if rec.area > 0:
                addr = mvc_addrtbl(server=rec.server, area=rec.area, address=rec.address, alter=rec.alter, xc=rec.xc, zc=rec.zc)
                addr_lst.append(addr)

        self.response.write("[mvc_addrtbl] Read %d entries</BR>\n" % len(addr_lst) )
        db.put(addr_lst)

        self.response.write("[mvc_headtbl] Read %d entries</BR>\n" % len(hdr_lst) )
        db.put(hdr_lst)
        return

    def del_attrs(self):

        str_max = self.request.get('max')
        ii_max = str2int(str_max)
        if ii_max == 0:
            ii_max = 20

        hdr_lst = []

        q = mvc_headtbl.all().filter('area >', 0)
        records = q.fetch(limit=ii_max)
        if len(records) == 0:
            self.response.write("All Done." )
            return

        cnt = 0
        for rec in records:
            cnt += 1
            if cnt < 10:
                self.response.write("[mvc_headtbl] %s</BR>\n" % rec.date )
            changed = 0
            if hasattr(rec, 'item_id'):
                delattr(rec, "item_id")
                changed += 1
            if hasattr(rec, 'server'):
                delattr(rec, "server")
                changed += 1
            if hasattr(rec, 'area'):
                delattr(rec, "area")
                changed += 1
            if hasattr(rec, 'alter'):
                delattr(rec, "alter")
                changed += 1
            if hasattr(rec, 'address'):
                delattr(rec, "address")
                changed += 1
            if hasattr(rec, 'xc'):
                delattr(rec, "xc")
                changed += 1
            if hasattr(rec, 'zc'):
                delattr(rec, "zc")
                changed += 1
            if changed > 0:
                hdr_lst.append(rec)

        self.response.write("[mvc_headtbl] Try to write %d entries</BR>\n" % len(hdr_lst) )
        db.put(hdr_lst)
        return

    # Wipe処理！ Debug環境以外では使用しないこと
    def wipe_header(self):
        self.response.write('Wiping header!')
        lst1 = mvc_headtbl.all().fetch(1000)
        db.delete(lst1)

    def wipe_item(self):
        self.response.write('Wiping!')
        lst2 = mvc_itemtbl.all().fetch(1000)
        db.delete(lst2)

    def unreview(self):
        # アイテム名称のレビューを初期化
        lst2 = mvc_itemtbl.all().filter('review < ', 0).fetch(1000)
        for rec in lst2:
            rec.review = 0
            db.put(rec)
        self.response.write('changed(Review): %d records.</BR>\n' % (len(lst2)) )

    def trim(self):
        # アイテム名称の空白を削るだけ
        mul = 100
        for i in range(0, 20):
            lst2 = mvc_itemtbl.all().fetch(limit=mul, offset=i*mul)
            rcnt = 0
            difcnt = 0
            for rec in lst2:
                rcnt += 1
                orig = rec.name
                len_bak = len(orig)
                #OK mod1 = re.sub('[ ]+$', '', orig)
                #OK mod2 = re.sub('^[ ]+', '', mod1)
                mod2 = orig.strip() #OK
                #self.response.write('[%03d] len1:%d\tlen2:%d name:%s|</BR>\n' % (rcnt, len_bak, len(mod2), orig) )
                if len_bak > len(mod2):
                    difcnt+=1
                    rec.name = mod2
                    db.put(rec)
            self.response.write('[%03d] changed(space): %d of %d records.</BR>\n' % (i, difcnt, rcnt))
            if rcnt == 0:
                break

class ImgMain(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        query_str = "SELECT * FROM moe_maptbl ORDER BY date DESC LIMIT 10"
        moe_maptbls = db.GqlQuery (query_str)

        for rec in moe_maptbls:
            if rec.author:
                self.response.out.write('<b>%s</b> wrote:' % rec.author.nickname())
            else:
                self.response.out.write('An anonymous person wrote:')
            self.response.out.write("<div><img src='img?img_id=%s'></img>" %
                                    rec.key())
            self.response.out.write(' %s</div>' %
                                  cgi.escape(rec.content))

        self.response.out.write("""
              <form action="/sign" enctype="multipart/form-data" method="post">
                <div><label>Message:</label></div>
                <div><textarea name="content" rows="3" cols="60"></textarea></div>
                <div><label>Avatar:</label></div>
                <div><input type="file" name="img"/></div>
                <div><input type="submit" value="Sign Guestbook"></div>
              </form>
            </body>
          </html>""")

class Image (webapp2.RequestHandler):
    def get(self):
        rec = db.get(self.request.get("img_id"))
        if rec.avatar:
            self.response.headers['Content-Type'] = "image/png"
            self.response.out.write(rec.avatar)
        else:
            self.response.out.write("No image")

class Guestbook(webapp2.RequestHandler):
    def post(self):
        rec = moe_maptbl()
        if users.get_current_user():
            rec.author = users.get_current_user()
        rec.content = self.request.get("content")
        avatar = self.request.get("img")
        rec.avatar = db.Blob(avatar)
        rec.put()
        self.redirect('/')

class gen_map(webapp2.RequestHandler):
    def get(self):
        img_back = None
        img_front = None

        xc = self.request.get("xc")
        zc = self.request.get("zc")

        # ゲーム内で言うゼロ地点
        ixc = 253
        izc = 255
        if xc:
            ixc += int(xc) *  1 * 32 / 100
        if zc:
            izc += int(zc) * -1 * 32 / 100
        #logging.info('X,Z: %s %s X,Y:%d,%d' % (xc, zc, ixc, izc))

        query_str = "SELECT * FROM moe_maptbl ORDER BY date DESC LIMIT 2"
        moe_maptbls = db.GqlQuery (query_str)

        for rec in moe_maptbls:
            if "sthis_map_background" == rec.content:
                img_back = rec.avatar
            if "moe_map_cursor" == rec.content:
                img_front = rec.avatar

        self.response.headers['Content-Type'] = "image/png"
        composite = images.composite([(img_back, 0, 0, 1.0, images.TOP_LEFT),
            (img_front, ixc, izc, 1.0, images.TOP_LEFT)], 509, 510)
        self.response.out.write(composite)

class Merge(webapp2.RequestHandler):

    def get(self):

        str_max = self.request.get('max')
        iMax = str2int(str_max)
        if iMax == 0:
            iMax = 1

        offset = self.request.get('offset')
        iOffset = str2int(offset)

        self.response.headers['Content-Type'] = 'text/plain'
        ent_list = mvc_itemtbl.all().order('name').fetch(limit=iMax, offset=iOffset)

        del_list = []
        for ent in ent_list:

            if ent.name in del_list:
                self.response.write("%s: already marked for delete\n" % (ent.name) )
                continue
            first_ent = ent
            ents_found = db.GqlQuery("select * from mvc_itemtbl where name = '%s'" % ent.name)
            ents_count = ents_found.count()
            if ents_count < 2:	
                continue
            self.response.write("%d entities found for %s\n" % (ents_count, ent.name) )

            for to_del in ents_found:
                # 一件目は、処理しない。
                if first_ent.key() == to_del.key():
                    continue

                # 2件目以降の従属する価格情報を最初に見つかったものに書き換える	
                hdr_list = []
                for hdr in to_del.mvc_headtbl_set:
                    hdr.item_ref = first_ent
                    hdr_list.append(hdr)
                    self.response.write("merged: %s</BR>\n" % hdr.price)
                db.put(hdr_list)

                del_list.append(to_del.name)
                self.response.write("%s: delete\n" % (to_del.name) )
                memcache.delete(to_del.name)
                db.delete(to_del)

        # db.delete(del_list)

class ShowBackup(webapp2.RequestHandler):

    def get(self):

        str_max = self.request.get('max')
        iMax = str2int(str_max)
        if iMax == 0:
            iMax = 1000

        offset = self.request.get('offset')
        iOffset = str2int(offset)

        #self.response.headers['Content-Type'] = 'text/plain'
        self.response.write("<HTML><HEAD><TITLE>Name modify backup</TITLE></HEAD><BODY>\n")
        self.response.write("<TABLE>\n")
        for ent in mvc_itembak.all():
            self.response.write("<TR>\n")
            self.response.write("<TD>\n")
            self.response.write("%s = %s\n" % (ent.name_old, ent.name) )
            self.response.write("</TD>\n")
            self.response.write("</TR>\n")
        self.response.write("</TABLE>\n")

app = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/PostDebug',  PostDebug),
    ('/PostAHK',    PostAHK),
    ('/PostAHK_Worker',    PostAHK_Worker),
    ('/PostItem',   PostItem),
    ('/Detail',     ItemDetail),
    ('/Modify',     Modify),
    ('/PostModify', PostModify),
    ('/Review', Review),
    ('/ReviewCmpl', ReviewCmpl),
    ('/DeleteItem', DeleteItem),
    ('/DeleteItemConfirm', DeleteItemConfirm),
    ('/DeletePrice', DeletePrice),
    ('/img/gen_map', gen_map),


    # メンテ用で普段は使わない。
#    ('/imgup', ImgMain),
#    ('/img', Image),
#    ('/sign', Guestbook),
    ('/Wipe', Wipe),      # for debug
    ('/Merge', Merge),
    ('/ShowBackup', ShowBackup),
], debug=True)
